package com.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.domain.LocalFile;
import com.domain.UserCollectionEntity;
import com.domain.Video;
import com.domain.VideoView;
import com.dto.PageResult;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mapper.LocalFileDao;
import com.mapper.UserCollectionMapper;
import com.mapper.VideoDao;
import com.service.VideoService;
import com.util.FastDFSUtil;
import com.util.IpUtil;
import com.util.StringUtils;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class VideoServiceImpl extends ServiceImpl<VideoDao, Video> implements VideoService {

    @Autowired
    private VideoDao videoDao;

    @Autowired
    private UserCollectionMapper userCollectionMapper;

    @Autowired
    private LocalFileDao fileDao;

    @Autowired
    private FastDFSUtil fastDFSUtil;

    @Autowired
    private RedisTemplate<String, String> redis;

    private static boolean ISASC = true;

    /**
     * 查询首页视频
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Map<String, List<Video>> homeListVideo(String videoPartition) {
        Map<String, List<Video>> mapReturn = new HashMap<>();
        // 只查询一个分区
        if (!videoPartition.isEmpty()) {
            // 模拟刷新效果
            ISASC = !ISASC;
            QueryWrapper<Video> videoPartitionQueryWrapper = new QueryWrapper<>();
            videoPartitionQueryWrapper.lambda().eq(Video::getVideoPartition, videoPartition)
                    .orderBy(true, ISASC, Video::getCreateTime).last(" limit 10");
            List<Video> videoPartitionList = videoDao.selectList(videoPartitionQueryWrapper);
            mapReturn.put(videoPartition + "List", videoPartitionList);
            return mapReturn;
        }
        Map<String, List<Video>> map;
        // 轮播图
        QueryWrapper<Video> videoQueryWrapper = new QueryWrapper<>();
        videoQueryWrapper.lambda().orderBy(true, false, Video::getCreateTime)
                .last(" limit 10");
        List<Video> videoList = videoDao.selectList(videoQueryWrapper);
        mapReturn.put("dougaList", videoList);
        // 分区
        List<Video> list = videoDao.homeListVideo();
        map = list.stream().collect(Collectors.groupingBy(Video::getVideoPartition));
        for (String key : map.keySet()) {
            List<Video> videos;
            videos = map.get(key);
            mapReturn.put(key + "List", videos);
        }
        return mapReturn;
    }

    /**
     * 给多个视频形成的列表分页
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public PageResult<Video> pageListVideos(Video video) {
        if (StringUtils.isNotEmpty(video.getVideoTitle()) && "请输入内容".equals(video.getVideoTitle())) {
            return new PageResult<>(0, new ArrayList<>());
        }
        // 分页查询
        PageHelper.startPage(video.getCurrentPage(), video.getCurrentSize());
        List<Video> result = videoDao.pageListVideo(video);
        PageInfo<Video> pageInfo = new PageInfo<>(result, video.getCurrentSize());
        return new PageResult<>((int) pageInfo.getTotal(), pageInfo.getList());
    }

    /**
     * 获取视频详细信息
     * TODO 一般来说，视频上传成功后，基本信息是不会变化的，所以可以把视频详细信息放在 redis 中存储，可以是 Hash 类型
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Video getVideoDetail(Long videoId, Long userId) {
        // 查询详情
        QueryWrapper<Video> videoQueryWrapper = new QueryWrapper<>();
        videoQueryWrapper.lambda().eq(Video::getId, videoId);
        Video video = videoDao.selectOne(videoQueryWrapper);
        if (video != null) {
            video.setVideoPlay(video.getVideoPlay() + 1);
            // 播放量+1
            videoDao.updateById(video);
            // 查询是否收藏了该视频
            QueryWrapper<UserCollectionEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(UserCollectionEntity::getUserId, userId).eq(UserCollectionEntity::getVideoId, videoId);
            UserCollectionEntity userCollectionEntity = userCollectionMapper.selectOne(queryWrapper);
            video.setHasCollection(userCollectionEntity != null);
            return video;
        } else {
            return null;
        }
    }

    /**
     * 添加视频信息
     */
    @Transactional
    @Override
    public void addVideo(Video video) {
        // 根据md5查询文件
        QueryWrapper<LocalFile> fileQueryWrapper = new QueryWrapper<>();
        fileQueryWrapper.lambda().eq(LocalFile::getFileMd5, video.getVideoUrl());
        LocalFile localFile = fileDao.selectOne(fileQueryWrapper);
        video.setCreateTime(new Date());
        video.setVideoPlay(0);
        video.setVideoDuration(localFile.getFileDuration());
        videoDao.insert(video);

//        Long videoId = video.getId();
//        List<VideoTag> videoTagList = video.getVideoTagList();
        //给该视频的标签列表批量添加“创建时间”和“videoId”
//        videoTagList.forEach(item -> {
//            item.setCreateTime(new Date());
//            item.setVideoId(videoId);
//        });
        //批量添加视频标签
//        videoDao.batchAddVideoTags(videoTagList);
    }


    /**
     * 在线观看视频（在线分片下载视频）
     */
    public void viewVideoOnlineBySlices(HttpServletRequest request,
                                        HttpServletResponse response,
                                        String url) throws Exception {
        fastDFSUtil.viewVideoOnlineBySlices(request, response, url);
    }

    /**
     * 功能：添加视频观看记录
     * <p>
     * 观看记录延伸的统计数据：播放量
     * 播放量技术规则：一个用户针对一个视频，一天内最多只能产生一次播放量。游客也是如此。
     * 但是如果用户观看过视频，退出登录后以游客身份观看视频，那就额外增加一次播放量
     * <p>
     * 游客区分规则：操作系统 + 浏览器 + ip
     */
    public void addVideoView(VideoView videoView, HttpServletRequest request) {
        Long videoId = videoView.getVideoId();
        Long userId = videoView.getUserId();

        //利用第三方工具类UserAgent来生成clientId
        String agent = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(agent);
        String clientId = String.valueOf(userAgent.getId());
        String ip = IpUtil.getIP(request);

        Map<String, Object> params = new HashMap<>();
        if (userId != null) {
            params.put("userId", userId);
        } else {
            params.put("ip", ip);
            params.put("clientId", clientId);
        }

        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        params.put("today", sdf.format(now));
        params.put("videoId", videoId);

        //添加观看记录
        VideoView dbVideoView = videoDao.getVideoView(params);
        if (dbVideoView == null) {
            videoView.setIp(ip);
            videoView.setClientId(clientId);
            videoView.setCreateTime(new Date());
            videoDao.addVideoView(videoView);
        }

        String key = "view-history:" + userId;
        addViewHistoryToRedis(key, String.valueOf(videoId), System.currentTimeMillis());
    }


    /**
     * 获取视频播放量
     * <p>
     * TODO 可以把 视频播放量放在 redis 中缓存一份
     */
    public Integer getVideoViewCounts(Long videoId) {
        return videoDao.getVideoViewCounts(videoId);
    }


    @Data
    public static class ScrollResult {
        //小于指定时间戳的集合
        private List<?> list;
        //本次查询的推送的最小时间戳
        private Long minTime;
        //偏移量
        private Integer offset;
    }

    /**
     * 获取用户历史记录（最多1000条历史记录）滚动查询，一次查一页，一页20条
     */
    public ScrollResult getViewHistory(Long userId, Long max, Integer offset) {

        String key = "view-history:" + userId;
        Set<ZSetOperations.TypedTuple<String>> set = redis.opsForZSet().reverseRangeByScoreWithScores(
                key, 0, max, offset, 20);
        if (CollectionUtils.isEmpty(set)) {
            return new ScrollResult();
        }

        List<Long> viewHistoryList = new ArrayList<>(set.size());
        long minTime = 0;
        int os = 1;
        for (ZSetOperations.TypedTuple<String> tuple : set) {
            // 获取videoId
            String videoId = tuple.getValue();
            viewHistoryList.add(Long.valueOf(videoId));
            // 获取时间戳
            long time = tuple.getScore().longValue();
            if (time == minTime) {
                os++;
            } else {
                minTime = time;
                os = 1;
            }
        }

        ScrollResult r = new ScrollResult();
        r.setList(viewHistoryList);
        r.setOffset(os);
        r.setMinTime(minTime);
        return r;
    }

    /**
     * 添加用户观看的历史记录（最多1000条）
     */
    public void addViewHistoryToRedis(String key, String value, long timestamp) {
        //1.判断该value是否已经存在
        Double score = redis.opsForZSet().score(key, value);
        if (score != null) {
            //2.如果存在，就直接add，redis会自动覆盖原有的值
            redis.opsForZSet().add(key, value, timestamp);
            return;
        }
        //3.如果不存在，就意味着要新增元素，需要先判断集合的长度是否<1000
        //3.1.先判断有序集合长度是否<1000
        Long size = redis.opsForZSet().size(key);
        //3.2.如果长度<1000，就新增元素
        if (size != null && size >= 1000) {
            //如果长度等于1000，就删除score最小的那个value，然后再新增元素
            //popMin方法是 redis 5.0 才推出的命令，意思是删除有序集合中最小的count个元素
            redis.opsForZSet().popMin(key, 1);
        }
        redis.opsForZSet().add(key, value, timestamp);
    }

}
